#include "Texture.h"
#include "NezhaIFileSystem.h"
#include "png.h"

namespace Nezha
{

	_Image2D::~_Image2D()
	{
		if(mBuffer)
		{
			NZFree(mBuffer);
		}
	}

	void _Image2D::allocate(u32 sizeInBytes)
	{
		if(!mBuffer)
		{
			mBuffer = (u8*)NZMalloc(sizeInBytes);
			mSizeInBytes = sizeInBytes;
		}
	}

	TextureManager::TextureManager()
		:mMaxUnusedID(1)
	{

	}

	TextureManager::~TextureManager()
	{
		removeAll();
	}

	TexturePtr TextureManager::create2DTexture(const _Image2D& img2d, bool mipmap, HardwareBuffer::Usage usage)
	{
		TexturePtr tex = _create2DTexture(img2d, mipmap, usage);

		if(tex.notNull())
		{
			u32 id = getUnusedTextureID();
			tex->_setID(id);
			insertTexture(tex);
		}

		return tex;
	}

	TexturePtr TextureManager::getTexture(u32 id)
	{
		TexturePtr tex;

		TextureIDMap::iterator it = mTextures.find(id);

		if(it != mTextures.end())
		{
			return it->second;
		}

		return tex;
	}

	void TextureManager::insertTexture(const TexturePtr& tex)
	{
		if(tex.notNull())
		{
			mTextures.insert(MapPair<u32, TexturePtr>(tex->getID(), tex));
		}
	}

	void TextureManager::_garbageDispose()
	{
		for(TextureIDMap::iterator it = mTextures.begin(); it != mTextures.end();)
		{
			if(it->second->getRefCount() == 1)
			{
				it->second.release();
				mTextures.erase(it++);
			}
			else
			{
				it++;
			}
		}
	}

	void TextureManager::removeAll()
	{
		for(TextureIDMap::iterator it = mTextures.begin(); it != mTextures.end(); it++)
		{
			it->second.release();
		}

		mTextures.clear();
		
		mMaxUnusedID = 1;
	}

	_Image2DPtr TextureManager::load2DImage(const _string& filePath)
	{
		// TODO

		_string fileExt = StringUtil::GetFileExt(filePath);
		for_each(fileExt.begin(), fileExt.end(), ::tolower);

		if(fileExt == "png")
		{
			return loadPNG(filePath);
		}

		if(fileExt == "pvr")
		{
			// TODO
		}

		if(fileExt == "dds")
		{

		}

		if(fileExt == "etc")
		{

		}

		NZError("Unsupported image file %s", filePath.c_str());

		_Image2DPtr empty;

		return empty;
	}

	static void PNG_ReadFunc(png_structp png, png_bytep data, png_size_t length)
	{
		stream::IReadFile* file = reinterpret_cast<stream::IReadFile*>(png_get_io_ptr(png));
		if (!file || file->read(data, length) != length)
		{
			png_error(png, "Error reading PNG.");
		}
	}

	_Image2DPtr TextureManager::loadPNG(const _string& filePath)
	{
		stream::IFileSystem* fileSys = stream::IFileSystem::GetInterface();
		stream::IReadFile* file = fileSys->createReadFile(filePath.c_str());

		_Image2DPtr imgPtr;

		// auto release the image file.
		ScopedPtr<stream::IReadFile>::NZNewMethod fileptr(file);

		if(!file)
		{
			NZError("Failed to load file %s", filePath.c_str());
			return imgPtr;
		}

		u8 sig[8] = {0};
		file->read(sig, 8);

		if(png_sig_cmp(sig, 0, 8) != 0)
		{
			NZError("Invalid png file [%s]", filePath.c_str());
			return imgPtr;
		}

		png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
		if (png == NULL)
		{
			NZError("Failed to create PNG structure for reading PNG file '%s'.", filePath.c_str());
			return imgPtr;
		}

		// Initialize info struct.
		png_infop info = png_create_info_struct(png);
		if (info == NULL)
		{
			NZError("Failed to create PNG info structure for PNG file '%s'.", filePath.c_str());
			png_destroy_read_struct(&png, NULL, NULL);
			return imgPtr;
		}

		// Set up error handling (required without using custom error handlers above).
		if (setjmp(png_jmpbuf(png)))
		{
			NZError("Failed to set up error handling for reading PNG file '%s'.", filePath.c_str());
			png_destroy_read_struct(&png, &info, NULL);
			return imgPtr;
		}

		// Initialize file io.
		png_set_read_fn(png, file, PNG_ReadFunc);

		// Indicate that we already read the first 8 bytes (signature).
		png_set_sig_bytes(png, 8);

		// Read the entire image into memory.
		png_read_png(png, info, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);

		_Image2D* img2D = NZ_New _Image2D();
		img2D->mWidth = (u16)png_get_image_width(png, info);
		img2D->mHeight = (u16)png_get_image_height(png, info);

		png_byte colorType = png_get_color_type(png, info);

		switch (colorType)
		{
		case PNG_COLOR_TYPE_RGBA:
			img2D->mFormat = TF_A8R8G8B8;
			break;
		case PNG_COLOR_TYPE_RGB:
			img2D->mFormat = TF_R8G8B8;
			break;

		default:
			NZError("Unsupported PNG color type (%d) for image file '%s'.", (int)colorType, filePath.c_str());
			png_destroy_read_struct(&png, &info, NULL);
			return imgPtr;
		}

		size_t stride = png_get_rowbytes(png, info);

		img2D->allocate(stride * img2D->mHeight);

		u8* imgBuf = img2D->getData();
		png_bytepp rows = png_get_rows(png, info);
		for (unsigned int i = 0; i < img2D->mHeight; ++i)
		{
			memcpy(imgBuf + (stride * (img2D->mHeight - 1 - i)), rows[i], stride);
		}

		png_destroy_read_struct(&png, &info, NULL);

		imgPtr = img2D;

		return imgPtr;
	}

	_Image2DPtr TextureManager::loadCompressedPVR(const _string& filePath)
	{
		stream::IFileSystem* fileSys = stream::IFileSystem::GetInterface();
		stream::IReadFile* file = fileSys->createReadFile(filePath.c_str());

		// auto release the image file.
		ScopedPtr<stream::IReadFile>::NZNewMethod fileptr(file);
		_Image2DPtr imgPtr;

		if(!file)
		{
			NZError("Failed to load file %s", filePath.c_str());
			return imgPtr;
		}

		size_t readBytes = 0;
		u32 versionNumber = 0;
		readBytes = file->read(&versionNumber, sizeof(u32));

		if(readBytes != sizeof(u32))
		{
			NZError("Invalid PVR file.");
			return imgPtr;
		}

		file->seek(0);

		_Image2D* img2d = NULL;

		if(versionNumber == 0x03525650)
		{
			// TODO load new PVR file 
		}
		else
		{
			// TODO Legacy PVR file format

		}



		return imgPtr;
	}

	u16 TextureManager::TexFormatBits(TexFormat fmt)
	{
		switch(fmt)
		{
		case TF_A8:
		case TF_L8:
		case TF_A4L4:
		case TF_PVR_RGBA2:
			return 8;

		case TF_R5G6B5:
		case TF_B5G6R5:
		case TF_A1R5G5B5:
		case TF_A1B5G5A5:
		case TF_A4R4G4B4:
		case TF_L16:
		case TF_A8L8:
		case TF_R16F:
		case TF_PVR_RGBA4:
			return 16;

		case TF_R8G8B8:
		case TF_B8G8R8:
			return 24;

		case TF_A8R8G8B8:
		case TF_A8B8G8R8:
		case TF_GR16:
		case TF_A2R10G10B10:
		case TF_A2B10G10R10:
		case TF_GR16F:
		case TF_R32F:
		case TF_DXT1:
		case TF_DXT2:
		case TF_DXT3:
		case TF_DXT4:
		case TF_DXT5:
		case TF_D24S8:
			return 32;

		case TF_RGB16:
		case TF_RGB16F:
			return 54;

		case TF_RGBA16:
		case TF_RGBA16F:
		case TF_GR32F:
			return 64;

		case TF_RGB32F:
			return 96;

		case TF_RGBA32F:
			return 128;

		case TF_PVR_RGB2:
			return 6;

		case TF_PVR_RGB4:
			return 12;
		}

		return 0;
	}

}//end namespace Nezha